import { ConfigStateService, CurrentUserDto } from '@abp/ng.core';
import { Component, Input, OnInit, TrackByFunction } from '@angular/core';
import { NzDrawerRef } from 'ng-zorro-antd/drawer';
import {
  GetPermissionListResultDto,
  IdentityUserService,
  PermissionGrantInfoDto,
  PermissionGroupDto,
  PermissionsService,
  ProviderInfoDto,
} from '../proxy/identity';

import snq from 'snq';
import { NzMessageService } from 'ng-zorro-antd/message';
import { switchMap } from 'rxjs/operators';
import { EntityTreeNode, EntityFlatNode, TreeComponent } from '../../core';
import { NzContextMenuService } from 'ng-zorro-antd/dropdown';

type NPermissionGrantInfoDto = PermissionGrantInfoDto & { groupName: string };

@Component({
  selector: 'app-ref-permission',
  templateUrl: './ref-permission.component.html',
  styleUrls: ['./ref-permission.component.less'],
})
export class RefPermissionComponent
  extends TreeComponent<NPermissionGrantInfoDto>
  implements OnInit
{
  @Input() providerName: string;
  @Input() providerKey: string;

  permissionList: GetPermissionListResultDto;
  currentGroup: PermissionGroupDto;
  trackByFn: TrackByFunction<PermissionGroupDto> = (_, item) => item.name;
  constructor(
    protected permissionsService: PermissionsService,
    protected configState: ConfigStateService,
    protected identityUserService: IdentityUserService,
    protected nzContextMenuService: NzContextMenuService,
    private drawerRef: NzDrawerRef<void>,
    private message: NzMessageService
  ) {
    super(false, nzContextMenuService);
  }

  ngOnInit(): void {
    this.permissionsService.get(this.providerName, this.providerKey).subscribe(permissionList => {
      this.permissionList = permissionList;

      let treeNodes: EntityTreeNode<NPermissionGrantInfoDto>[] = [];
      for (let group of permissionList.groups) {
        treeNodes = [...treeNodes, ...this.getGroupTreeNodes(group)];
      }

      this.dataSource.setData(treeNodes);
      this.treeControl.expandAll();
    });
  }

  getGroupTreeNodes(group: PermissionGroupDto): EntityTreeNode<NPermissionGrantInfoDto>[] {
    return this.createTreeNodes(group.name, null, group.permissions);
  }

  createTreeNodes(
    groupName: string,
    parentName: string,
    permissions: PermissionGrantInfoDto[]
  ): EntityTreeNode<NPermissionGrantInfoDto>[] {
    let treeNodes: EntityTreeNode<NPermissionGrantInfoDto>[] = [];
    for (let permission of permissions.filter(x => x.parentName == parentName)) {
      const children = this.createTreeNodes(groupName, permission.name, permissions);
      treeNodes = [
        ...treeNodes,
        {
          ...permission,
          key: `${groupName}-${permission.name}`,
          children,
          parentName: parentName,
          groupName,
          disabled: !!permission.grantedProviders.filter(x => x.providerKey != this.providerKey)
            .length,
        },
      ];
    }

    return treeNodes;
  }

  getTabTitle(group: PermissionGroupDto): string {
    return `${group.displayName}(${this.getNodesSelectedCount(group)})`;
  }

  getNodesSelectedCount(group: PermissionGroupDto): number {
    return snq(
      () => this.treeControl.dataNodes.filter(x => x.groupName == group.name && x.isGranted).length
    );
  }

  onTabChanged(group: PermissionGroupDto): void {
    this.currentGroup = group;
  }

  getProviders(node: EntityFlatNode<NPermissionGrantInfoDto>): ProviderInfoDto[] {
    return node.grantedProviders.filter(x => x.providerName != this.providerName);
  }

  selectionToggle(node: EntityFlatNode<NPermissionGrantInfoDto>): void {
    node.isGranted = !node.isGranted;

    if (node.isGranted) {
      if (!!node.parentName) {
        let parent = this.treeControl.dataNodes.find(x => x.name == node.parentName && !x.disabled);
        if (!!parent) parent.isGranted = node.isGranted;
      }
    }

    var children = this.treeControl.getDescendants(node)?.filter(x => !x.disabled);
    if (!!children?.length) {
      for (let child of children) {
        child.isGranted = node.isGranted;
      }
    }
  }

  isGroupAllGranted(group: PermissionGroupDto): boolean {
    return snq(() =>
      this.treeControl.dataNodes.filter(x => x.groupName == group.name).every(x => x.isGranted)
    );
  }

  isGroupIndeterminate(group: PermissionGroupDto): boolean {
    return snq(
      () =>
        this.treeControl.dataNodes.filter(x => x.groupName == group.name).some(x => x.isGranted) &&
        !this.treeControl.dataNodes.filter(x => x.groupName == group.name).every(x => x.isGranted)
    );
  }

  isGroupDisabled(group: PermissionGroupDto) {
    return snq(() =>
      this.treeControl.dataNodes.filter(x => x.groupName == group.name).every(x => x.disabled)
    );
  }

  groupSelectionToggle(group: PermissionGroupDto, isGranted: boolean): void {
    for (let treeNode of this.treeControl.dataNodes.filter(
      x => x.groupName == group.name && !x.disabled
    )) {
      treeNode.isGranted = isGranted;
    }
  }

  isAllGranted(): boolean {
    return snq(() => this.treeControl.dataNodes.every(x => x.isGranted));
  }

  isIndeterminate(): boolean {
    return snq(
      () =>
        this.treeControl.dataNodes.some(x => x.isGranted) &&
        !this.treeControl.dataNodes.every(x => x.isGranted)
    );
  }

  isAllDisabled() {
    return snq(() => this.treeControl.dataNodes.every(x => x.disabled));
  }

  allSelectionToggle(isGranted: boolean): void {
    for (let treeNode of this.treeControl.dataNodes.filter(x => !x.disabled)) {
      treeNode.isGranted = isGranted;
    }
  }

  close(): void {
    this.drawerRef.close();
  }

  save(): void {
    let changedPermission = this.getChangedPermission();
    if (!changedPermission.length) return;

    this.permissionsService
      .update(this.providerName, this.providerKey, {
        permissions: changedPermission,
      })
      .pipe(switchMap(() => this.permissionsService.get(this.providerName, this.providerKey)))
      .subscribe(permissionList => {
        this.permissionList = permissionList;

        let treeNodes: EntityTreeNode<NPermissionGrantInfoDto>[] = [];
        for (let group of permissionList.groups) {
          treeNodes = [...treeNodes, ...this.getGroupTreeNodes(group)];
        }

        this.message.success('更新成功！');
        if (this.shouldFetchAppConfig()) this.configState.refreshAppState();
      });
  }

  getChangedPermission(): PermissionGrantInfoDto[] {
    let changedPermission: PermissionGrantInfoDto[] = [];
    if (!this.permissionList) return changedPermission;

    for (let group of this.permissionList.groups) {
      for (let permission of group.permissions) {
        let treeNode = this.treeControl.dataNodes.find(
          x => x.name == permission.name && x.groupName == group.name
        );
        if (treeNode.isGranted != permission.isGranted) {
          changedPermission = [
            ...changedPermission,
            {
              ...permission,
              isGranted: treeNode.isGranted,
            },
          ];
        }
      }
    }
    return changedPermission;
  }

  shouldFetchAppConfig(): boolean {
    const currentUser = this.configState.getOne('currentUser') as CurrentUserDto;

    if (this.providerName === 'R') return currentUser.roles.some(role => role === this.providerKey);
    if (this.providerName === 'U') return currentUser.id === this.providerKey;

    return false;
  }

  createEntityTreeNodes(
    list: NPermissionGrantInfoDto[],
    parent: NPermissionGrantInfoDto,
    parentIdPropretyName?: string,
    idPropertyName?: string
  ): EntityTreeNode<NPermissionGrantInfoDto>[] {
    throw new Error('use createTreeNodes instead.');
  }

  createEntityTreeNode(model: NPermissionGrantInfoDto): EntityTreeNode<NPermissionGrantInfoDto> {
    throw new Error('Method not implemented.');
  }
}
